package arithm

import (
	"fmt"
	"math/big"
)

type NumberType int

const (
	INTEGER_TYPE NumberType = iota
	BIGINTEGER_TYPE
	RATIONAL_TYPE
	REAL_TYPE
	COMPLEX_TYPE
)

type Integer int64

type BigInteger big.Int
type Rational big.Rat
type Real float64
type Complex complex128

type Number interface {
	Type() NumberType // Returns the number type
	String() string   // Convert to string representation

	// Arithmetic operations - following R5RS precision rules
	ADD(Number) Number // Addition
	SUB(Number) Number // Subtraction
	MUL(Number) Number // Multiplication
	DIV(Number) Number // Division
	NEG() Number       // Negation
}

func RationalToIntegerOrBigInteger(x *Rational) Number {
	result := (*big.Rat)(x)
	if result.Denom().Cmp(big.NewInt(1)) == 0 {
		numerator := result.Num()
		// Check if numerator fits in int64 range
		if numerator.IsInt64() {
			return Integer(numerator.Int64())
		}
		return (*BigInteger)(numerator)
	}
	return (*Rational)(result)
}

func BigIntegerToInteger(x *BigInteger) Number {
	result := (*big.Int)(x)
	if result.IsInt64() {
		return Integer(result.Int64())
	}
	return x
}

// Integer ----------------------------

func (x Integer) Type() NumberType {
	return INTEGER_TYPE
}

func (x Integer) String() string {
	return big.NewInt(int64(x)).String()
}

func (x Integer) ADD(y Number) Number {
	switch y := y.(type) {
	case Integer:
		if (int64(x) > 0 && int64(y) > 0 && int64(x) > (1<<63-1-int64(y))) ||
			(int64(x) < 0 && int64(y) < 0 && int64(x) < (-1<<63-int64(y))) {
			// Overflow occurred, convert to BigInteger
			bigResult := new(big.Int).Add(big.NewInt(int64(x)), big.NewInt(int64(y)))
			return (*BigInteger)(bigResult)
		}
		return Integer(int64(x) + int64(y))
	case *BigInteger, *Rational, Real, Complex:
		return y.ADD(x)
	default:
		return nil
	}
}

func (x Integer) SUB(y Number) Number {
	return x.ADD(y.NEG())
}

func (x Integer) MUL(y Number) Number {
	switch y := y.(type) {
	case Integer:
		result := int64(x) * int64(y)
		// Check for overflow
		if int64(x) == 0 || int64(y) == 0 {
			return Integer(0)
		}
		if result/int64(x) == int64(y) {
			return Integer(result)
		}
		// Overflow occurred
		bigResult := BigInteger(*new(big.Int).Mul(big.NewInt(int64(x)), big.NewInt(int64(y))))
		return &bigResult
	case *BigInteger, *Rational, Real, Complex:
		return y.MUL(x)
	default:
		return nil
	}
}

func (x Integer) DIV(y Number) Number {
	switch y := y.(type) {
	case Integer:
		ratResult := new(big.Rat).SetFrac(big.NewInt(int64(x)), big.NewInt(int64(y)))
		return RationalToIntegerOrBigInteger((*Rational)(ratResult))
	case *BigInteger:
		ratResult := new(big.Rat).SetFrac(big.NewInt(int64(x)), (*big.Int)(y))
		return RationalToIntegerOrBigInteger((*Rational)(ratResult))
	case *Rational:
		ratX := big.NewRat(int64(x), 1)
		ratY := (*big.Rat)(y)
		result := new(big.Rat).Quo(ratX, ratY)
		return RationalToIntegerOrBigInteger((*Rational)(result))
	case Real:
		return Real(float64(x) / float64(y))
	case Complex:
		return Complex(complex(float64(x), 0) / complex128(y))
	default:
		return nil
	}
}

func (x Integer) NEG() Number {
	return Integer(-int64(x))
}

// BigInteger ----------------------------

func (x *BigInteger) Type() NumberType {
	return BIGINTEGER_TYPE
}
func (x *BigInteger) String() string {
	return (*big.Int)(x).String()
}

func (x *BigInteger) ADD(y Number) Number {
	switch y := y.(type) {
	case Integer:
		bigResult := new(big.Int).Add((*big.Int)(x), big.NewInt(int64(y)))
		return BigIntegerToInteger((*BigInteger)(bigResult))
	case *BigInteger:
		bigResult := new(big.Int).Add((*big.Int)(x), (*big.Int)(y))
		return BigIntegerToInteger((*BigInteger)(bigResult))
	case *Rational, Real, Complex:
		return y.ADD(x)
	default:
		return nil
	}
}

func (x *BigInteger) SUB(y Number) Number {
	return x.ADD(y.NEG())
}

func (x *BigInteger) MUL(y Number) Number {
	switch y := y.(type) {
	case Integer:
		result := new(big.Int).Mul((*big.Int)(x), big.NewInt(int64(y)))
		return (*BigInteger)(result)
	case *BigInteger:
		result := new(big.Int).Mul((*big.Int)(x), (*big.Int)(y))
		return (*BigInteger)(result)
	case *Rational, Real, Complex:
		return y.MUL(x)
	default:
		return nil
	}
}

func (x *BigInteger) DIV(y Number) Number {
	switch y := y.(type) {
	case Integer:
		ratResult := new(big.Rat).SetFrac((*big.Int)(x), big.NewInt(int64(y)))
		return RationalToIntegerOrBigInteger((*Rational)(ratResult))
	case *BigInteger:
		ratResult := new(big.Rat).SetFrac((*big.Int)(x), (*big.Int)(y))
		return RationalToIntegerOrBigInteger((*Rational)(ratResult))
	case *Rational:
		ratX := new(big.Rat).SetFrac((*big.Int)(x), new(big.Int).SetInt64(1))
		ratY := (*big.Rat)(y)
		result := new(big.Rat).Quo(ratX, ratY)
		return RationalToIntegerOrBigInteger((*Rational)(result))
	case Real:
		f, _ := (*big.Int)(x).Float64()
		return Real(f / float64(y))
	case Complex:
		f, _ := (*big.Int)(x).Float64()
		return Complex(complex(f, 0) / complex128(y))
	default:
		return nil
	}
}

func (x *BigInteger) NEG() Number {
	result := new(big.Int).Neg((*big.Int)(x))
	return BigIntegerToInteger((*BigInteger)(result))
}

// Rational ----------------------------
func (x *Rational) Type() NumberType {
	return RATIONAL_TYPE
}

func (x *Rational) String() string {
	return (*big.Rat)(x).String()
}

func (x *Rational) ADD(y Number) Number {
	switch y := y.(type) {
	case Integer:
		r := new(big.Rat).Add((*big.Rat)(x), new(big.Rat).SetInt64(int64(y)))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case *BigInteger:
		r := new(big.Rat).Add((*big.Rat)(x), new(big.Rat).SetFrac((*big.Int)(y), new(big.Int).SetInt64(1)))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case *Rational:
		r := new(big.Rat).Add((*big.Rat)(x), (*big.Rat)(y))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case Real, Complex:
		return y.ADD(x)
	default:
		return nil
	}
}

func (x *Rational) SUB(y Number) Number {
	return x.ADD(y.NEG())
}

func (x *Rational) MUL(y Number) Number {
	switch y := y.(type) {
	case Integer:
		r := new(big.Rat).Mul((*big.Rat)(x), new(big.Rat).SetInt64(int64(y)))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case *BigInteger:
		r := new(big.Rat).Mul((*big.Rat)(x), new(big.Rat).SetFrac((*big.Int)(y), new(big.Int).SetInt64(1)))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case *Rational:
		r := new(big.Rat).Mul((*big.Rat)(x), (*big.Rat)(y))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case Real, Complex:
		return y.MUL(x)
	default:
		return nil
	}
}

func (x *Rational) DIV(y Number) Number {
	switch y := y.(type) {
	case Integer:
		r := new(big.Rat).Quo((*big.Rat)(x), new(big.Rat).SetInt64(int64(y)))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case *BigInteger:
		r := new(big.Rat).Quo((*big.Rat)(x), new(big.Rat).SetFrac((*big.Int)(y), new(big.Int).SetInt64(1)))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case *Rational:
		r := new(big.Rat).Quo((*big.Rat)(x), (*big.Rat)(y))
		return RationalToIntegerOrBigInteger((*Rational)(r))
	case Real, Complex:
		return y.DIV(x)
	default:
		return nil
	}
}

func (x *Rational) NEG() Number {
	result := new(big.Rat).Neg((*big.Rat)(x))
	return RationalToIntegerOrBigInteger((*Rational)(result))

}

// Real ----------------------------
func (x Real) Type() NumberType {
	return REAL_TYPE
}

func (x Real) String() string {
	return big.NewFloat(float64(x)).String()
}

func (x Real) ADD(y Number) Number {
	switch y := y.(type) {
	case Integer:
		return Real(float64(x) + float64(y))
	case *BigInteger:
		f, _ := (*big.Int)(y).Float64()
		return Real(float64(x) + f)
	case *Rational:
		f, _ := (*big.Rat)(y).Float64()
		return Real(float64(x) + f)
	case Real:
		return Real(float64(x) + float64(y))
	case Complex:
		return Complex(complex(float64(x), 0) + complex128(y))
	default:
		return nil
	}
}
func (x Real) SUB(y Number) Number {
	return x.ADD(y.NEG())
}

func (x Real) MUL(y Number) Number {
	switch y := y.(type) {
	case Integer:
		return Real(float64(x) * float64(y))
	case *BigInteger:
		f, _ := (*big.Int)(y).Float64()
		return Real(float64(x) * f)
	case *Rational:
		f, _ := (*big.Rat)(y).Float64()
		return Real(float64(x) * f)
	case Real:
		return Real(float64(x) * float64(y))
	case Complex:
		return Complex(complex(float64(x), 0) * complex128(y))
	default:
		return y
	}
}

func (x Real) DIV(y Number) Number {
	switch y := y.(type) {
	case Integer:
		return Real(float64(x) / float64(y))
	case *BigInteger:
		f, _ := (*big.Int)(y).Float64()
		return Real(float64(x) / f)
	case *Rational:
		f, _ := (*big.Rat)(y).Float64()
		return Real(float64(x) / f)
	case Real:
		return Real(float64(x) / float64(y))
	case Complex:
		return Complex(complex(float64(x), 0) / complex128(y))
	default:
		return y
	}
}

func (x Real) NEG() Number {
	return -x
}

// Complex ----------------------------
func (x Complex) Type() NumberType {
	return COMPLEX_TYPE
}

func (x Complex) String() string {
	s := fmt.Sprintf("%g", complex128(x))
	return s[1 : len(s)-1]
}

func (x Complex) ADD(y Number) Number {
	switch y := y.(type) {
	case Integer:
		return Complex(complex128(x) + complex(float64(y), 0))
	case *BigInteger:
		f, _ := (*big.Int)(y).Float64()
		return Complex(complex128(x) + complex(float64(f), 0))
	case *Rational:
		f, _ := (*big.Rat)(y).Float64()
		return Complex(complex128(x) + complex(float64(f), 0))
	case Real:
		return Complex(complex128(x) + complex(float64(y), 0))
	case Complex:
		return Complex(complex128(x) + complex128(y))
	default:
		return nil
	}
}

func (x Complex) SUB(y Number) Number {
	return x.ADD(y.NEG())
}

func (x Complex) MUL(y Number) Number {
	switch y := y.(type) {
	case Integer:
		return Complex(complex128(x) * complex(float64(y), 0))
	case *BigInteger:
		f, _ := (*big.Int)(y).Float64()
		return Complex(complex128(x) * complex(float64(f), 0))
	case *Rational:
		f, _ := (*big.Rat)(y).Float64()
		return Complex(complex128(x) * complex(float64(f), 0))
	case Real:
		return Complex(complex128(x) * complex(float64(y), 0))
	case Complex:
		return Complex(complex128(x) * complex128(y))
	default:
		return nil
	}
}

func (x Complex) DIV(y Number) Number {
	switch y := y.(type) {
	case Integer:
		return Complex(complex128(x) / complex(float64(y), 0))
	case *BigInteger:
		f, _ := (*big.Int)(y).Float64()
		return Complex(complex128(x) / complex(float64(f), 0))
	case *Rational:
		f, _ := (*big.Rat)(y).Float64()
		return Complex(complex128(x) / complex(float64(f), 0))
	case Real:
		return Complex(complex128(x) / complex(float64(y), 0))
	case Complex:
		return Complex(complex128(x) / complex128(y))
	default:
		return nil
	}
}

func (x Complex) NEG() Number {
	return -x
}
